All raster operations in this topic are accomplished using the raster library.
library(raster)
Loading required package: sp
Raster are representations of continuous, or semi-continuous, data. TYou can envision a raster just like an image. When me make a leaflet() map and how the tiles, each pixel is colored a particular value representing elevation, temperature, precipitation, habitat type, or whatever. This is exactly the same for rasters. The key point here is that each pixel represents some defined region on the earth and as such the raster itself is georeferenced. It has a coordinate reference system (CRS), boundaries, etc.
Making Rasters de novo
A raster is simply a matrix with rows and columns and each element has a value associated with it. You can create a raster de novo by making a matrix of data and filling it with values, then turning it into a raster.
Here I make a raster with random numbrers selected from the Poisson Distribution (fishy, I know) using the rpois() function. I then turn it into a matrix with 7 rows (and 7 columns).
vals <- rpois(49, lambda=12)
x <- matrix( vals, nrow=7)
x
[,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,] 8 17 7 14 11 9 8
[2,] 12 9 5 10 9 11 16
[3,] 13 6 11 8 8 13 14
[4,] 7 9 11 10 12 9 10
[5,] 12 15 19 17 13 13 7
[6,] 12 10 8 15 9 15 13
[7,] 14 10 11 14 15 9 9
While we haven’t used matrices much thus far, it is a lot like a data.frame with respect to getting and setting values using numerical indices. For example, the value of the 3rd row and 5th column is:
x[3,5]
[1] 8
To convert this set of data, as a matrix, into a geospatially referenced raster() object we do the following:
r <- raster( x )
r
class : RasterLayer
dimensions : 7, 7, 49 (nrow, ncol, ncell)
resolution : 0.1428571, 0.1428571 (x, y)
extent : 0, 1, 0, 1 (xmin, xmax, ymin, ymax)
crs : NA
source : memory
names : layer
values : 5, 19 (min, max)
Notice that when I plot it out, it does not show the data, but a summary of the data along with some key data about the contents, including:
- A class definition
- The dimensions of the underlying data matrix,
- The resolution (e.g., the spatial extent of the sides of each pixel). Since we have no CRS here, it is equal to \(nrows(x)^{-1}\) and \(ncols(x)^{-1}\).
- The extent (the bounding box) and again since we do not have a CRS defined it just goes from \(0\) to \(1\). - The crs (missing) - The source can be either memory if the raster is not that big or out of memory if it is just referencing.
If these data represent something on the planet, we can assign the dimensions and CRS values to it and use it in our normal day-to-day operations.
Loading Rasters from Files or URLs
We can also grab a raster object from the filesystem or from some online repository by passing the link to the raster() function. Here is the elevation, in meters, of the region in which Mexico is found. To load it in, pass the url.
url <- "https://github.com/dyerlab/ENVS-Lectures/raw/master/data/alt_22.tif"
r <- raster( url )
r
class : RasterLayer
dimensions : 3600, 3600, 12960000 (nrow, ncol, ncell)
resolution : 0.008333333, 0.008333333 (x, y)
extent : -120, -90, 0, 30 (xmin, xmax, ymin, ymax)
crs : +proj=longlat +datum=WGS84 +no_defs
source : https://github.com/dyerlab/ENVS-Lectures/raw/master/data/alt_22.tif
names : alt_22
values : -202, 5469 (min, max)
Notice that this raster has a defined CRS and as such it is projected and the extent relates to the units of the datum (e.g., from -120 to -90 degrees longitude and 0 to 30 degrees latitude).
If we plot it, we can see the whole raster.
plot(r)

Now, this raster is elevation where there is land but where there is no land, it is full of NA values. As such, there is a ton of them.
format( sum( is.na( values(r) ) ), big.mark = "," )
[1] "10,490,650"
10,490,650
Cropping
One of the first things to do is to crop the data down to represent the size and extent of our study area. If we over 10 million missing data points (the ocean) and most of Mexico in this raster above but we are only working with sites in Baja California (Norte y Sur), we would do well to excise (or crop) the raster to only include the area we are interested in working with.
Top do this, we need to figure out a bounding box (e.g., the minimim and maximum values of longitude and latitude that enclose our data). Let’s assume we are working with the Beetle Data from the Spatial Points Slides and load in the Sex-biased dispersal data set and use those points as a starting estimate of the bounding box.
library( sf )
Linking to GEOS 3.8.1, GDAL 3.1.1, PROJ 6.3.1
library( tidyverse )
── Attaching packages ─────────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
✓ ggplot2 3.3.2 ✓ purrr 0.3.4
✓ tibble 3.0.3 ✓ dplyr 1.0.2
✓ tidyr 1.1.2 ✓ stringr 1.4.0
✓ readr 1.3.1 ✓ forcats 0.5.0
── Conflicts ────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x tidyr::extract() masks raster::extract()
x dplyr::filter() masks stats::filter()
x dplyr::lag() masks stats::lag()
x dplyr::select() masks raster::select()
beetle_url <- "https://raw.githubusercontent.com/dyerlab/ENVS-Lectures/master/data/Araptus_Disperal_Bias.csv"
read_csv( beetle_url ) %>%
st_as_sf( coords=c("Longitude","Latitude"), crs=4326 ) -> beetles
Parsed with column specification:
cols(
Site = col_character(),
Males = col_double(),
Females = col_double(),
Suitability = col_double(),
MFRatio = col_double(),
GenVarArapat = col_double(),
GenVarEuphli = col_double(),
Latitude = col_double(),
Longitude = col_double()
)
summary( beetles )
Site Males Females Suitability
Length:31 Min. : 9.00 Min. : 5.00 Min. :0.0563
Class :character 1st Qu.:16.00 1st Qu.:15.50 1st Qu.:0.2732
Mode :character Median :21.00 Median :21.00 Median :0.3975
Mean :25.68 Mean :23.52 Mean :0.4276
3rd Qu.:31.50 3rd Qu.:29.00 3rd Qu.:0.5442
Max. :64.00 Max. :63.00 Max. :0.9019
MFRatio GenVarArapat GenVarEuphli geometry
Min. :0.5938 Min. :0.0500 Min. :0.0500 POINT :31
1st Qu.:0.8778 1st Qu.:0.1392 1st Qu.:0.1777 epsg:4326 : 0
Median :1.1200 Median :0.2002 Median :0.2171 +proj=long...: 0
Mean :1.1598 Mean :0.2006 Mean :0.2203
3rd Qu.:1.3618 3rd Qu.:0.2592 3rd Qu.:0.2517
Max. :2.2000 Max. :0.3379 Max. :0.5122
Now, we can take the bounding box of these points and get a first approximation.
beetles %>% st_bbox()
xmin ymin xmax ymax
-114.29353 23.28550 -109.32700 29.32541
OK, so this is the strict bounding box for these points. This means that the minimum and maximum values for these points are defined by the original locations—for both the latitude and longitude (both minimum and maximum)—we have sites on each of the edges. This is fine here but we could probably add a little bit of a buffer around that bounding box so that we do not have our sites on the very edge of the plot. We can do this by either eyeballing-it to round up to some reasonable area around the points or apply a buffer (st_buffer) to the union of all the points with some distance and then take the boounding box. I’ll go for the former and make it into an extent object.
baja_extent <- extent( c(-116, -109, 22, 30 ) )
baja_extent
class : Extent
xmin : -116
xmax : -109
ymin : 22
ymax : 30
Then we can crop() the original raster using this extent object to create our working raster. I can then dump my points onto the same raster plot by indicaating add=TRUE
alt <- crop( r, baja_extent )
plot(alt)
plot( beetles["Suitability"], pch=16, add=TRUE)

⚠️
|
|
You need to be careful here. When you use built-in graphics processes in a markdown document such as this and intend to add subsequent plots to an existing plot you cannot run the lines individuall. They must be all executed as the whole chunk. So there is no CTRL/CMD + RETURN action here, it will plot the first one and then complain throughout the remaining ones saying something like plot.new has not been called yet. So you have to either knit the whole document or just run the whole chunk to get them to overlay.
|
Plotting with GGPlot
As you may suspect, our old friend ggplot has some tricks up its sleave for us. The main thing here is that ggplot requires a data.frame object and a raster is not a data.frame — Unless we turn it into one (hehehe) using a cool function called rasterToPoints(). This takes the cells of the raster (and underlying matrix) and makes points from it.
alt %>%
rasterToPoints() %>%
head()
x y alt_22
[1,] -115.7958 29.99583 55
[2,] -115.7875 29.99583 126
[3,] -115.7792 29.99583 94
[4,] -115.7708 29.99583 99
[5,] -115.7625 29.99583 106
[6,] -115.7542 29.99583 120
However, they are not a data.frame but a matrix.
alt %>%
rasterToPoints() %>%
class()
[1] "matrix" "array"
matrix
array
So, if we are going to use this, w need to transform it from a matrix object into a data.frame object. We can do this using the as.data.frame() function. Remember from the lecture on data.frame objects that we can coerce columns of data (either matrix or array) into a data.frame this way.
So here it is in one pipe, using the following tricks:
- Converting raster to points and then to data.frame so it will go into ggplot
- Renaming the columns of data I am going to keep so I don’t have to make xlab and ylab
alt %>%
rasterToPoints() %>%
as.data.frame() %>%
transmute(Longitude=x,
Latitude=y,
Elevation=alt_22) -> alt.df
head( alt.df )
Then we can plot it by:
- Plotting it using geom_raster() and setting the fill color to the value of elevation. - Making the coordinates equal (e.g., roughtly equal in area for longitude and latitude), and - Applying only a minimal theme.
alt.df %>%
ggplot() +
geom_raster( aes( x = Longitude,
y = Latitude,
fill = Elevation) ) +
coord_equal() +
theme_minimal() -> baja_elevation
baja_elevation

That looks good but we should probably do something with the colors. There is a built-in terrain.colors() and tell ggplot to use this for the fill gradient.
baja_elevation +
scale_fill_gradientn( colors=terrain.colors(100))

Or you can go dive into colors and set your own, you can set up your own gradient for ggplot using independent colors and then tell it where the midpoint is along that gradient and it will do the right thing©.
baja_elevation +
scale_fill_gradient2( low = "darkolivegreen",
mid = "yellow",
high = "brown",
midpoint = 1000 ) -> baja_map
baja_map

Now that looks great. Now, how about overlaying the points onto the plot and indicate the size of the point by the ♂♀ ratio.
baja_map +
geom_sf( aes(size = MFRatio ),
data = beetles,
color = "dodgerblue2",
alpha = 0.75)

Now that looks nice.
Identifying Points
You can get some information from a raster plot interactively by using the click function. This must be done with an active raster plot. After that, you use the click() function to grab what you need. Your mouse will turn from an arrow into a cross hair and you can position it where you like and get information such as the corrdinates (spatial) of the point and the value of the raster pixel at that location.
If you do not specify n= in the function then it will continue to collect data until you click outside the graphing area. If you set id=TRUE it will plot the number of the point onto the map so you can see where you had clicked. Since this is interactive, you will not see the process when you execute the code below, but it will look like.
plot( alt )
click(alt, xy=TRUE, value=TRUE, n=3 ) -> points
Here are what the points look like.
points
I’m going to rename the column names
points %>%
transmute( Longitude = x,
Latitude = y,
Value = value) -> sites
And then I can plot those points (using geom_point()) onto our background map.
baja_map +
geom_point( aes(x = Longitude,
y = Latitude,
size = Value), data=sites, color="red")

Mexellent!
Reprojecting Rasters
Just like points, we can reproject the entire raster using the projectRaster function. HJere I am going to project the raster into UTM Zone 12N, a common projection for this part of Mexico from epsg.io.
Unfortunatly, the raster library does not use epsg codes so we’ll have to use the large description of that projection. See the page for this projection and scroll down to the proj.4 definition.
new.proj <- "+proj=utm +zone=12 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "
Copy this into a character variable and then use the projectRaster() function and assign that new value as the CRS.
alt.utm <- projectRaster( alt, crs=new.proj)
plot( alt.utm, xlab="Easting", ylab="Northing" )

Easy.
Raster Operations
OK, so now we can make and show a raster but what about doing some operations? A raster is just a matrix decorated with more geospatial information. This allows us to do normal R like data manipulations on the underlying data.
Consider the following question.
What are the parts of Baja California that are within 100m of the elevation of site named San Francisquito (sfran)?
To answer this, we have the following general outline of operations.
- Find the coordinates of the site named
sfran
- Extract the elevation from the
alt raster that is within 100m (+/-) of that site.
- Plot the whole baja data as a background
- Overlay all the locations within that elevation band.
To do this we will use both the alt and the beetles data objects.
First, we find out the coordinates of the site.
sfran <- beetles$geometry[ beetles$Site == "sfran"]
sfran
Geometry set for 1 feature
geometry type: POINT
dimension: XY
bbox: xmin: -112.964 ymin: 27.3632 xmax: -112.964 ymax: 27.3632
geographic CRS: WGS 84
POINT (-112.964 27.3632)
Now, we need to figure out what the value of elevation in the alt raster is at this site. This can be done with the extract() function from the raster library.
However, the this function doesn’t work directly with sf objects so we need to cast it into a Spatial object. Fortunatly, that is a pretty easy coercion.
raster::extract(alt, as(sfran,"Spatial") )
[1] 305
Warning: in the above code, I used the function extract() to extract the data from the alt raster for the coordinate of the target locale. However, there is also an extract() function that has been brought in from the dplyr library (as part of tidyverse). In this file, I loaded library(raster) before library(tidyverse) and as such the dplyr::extract() function has overridden the one from raster—they cannot both be available. As a consequence, I use the full name of the function with package::function when I call it as raster::extract() to remove all ambiguity. If I had not, I got a message saying something like, Error in UseMethod("extract_") : no applicable method for 'extract_' applied to an object of class "c('RasterLayer', 'Raster', 'BasicRaster')". Now, I know there is an extract() function in raster so this is the dead giveaway that it has been overwritten by a subsequent library call.
Option 1 - Manipulate the Raster
To work on a raster directly, we can access the values within it using the values() function (I know, these statistican/programmers are quite cleaver).
So, to make a copy and make only the values that are +/- 100m of sfran we can.
alt_band <- alt
values( alt_band )[ values(alt_band) <= 205 ] <- NA
values( alt_band )[ values(alt_band) >= 405 ] <- NA
alt_band
class : RasterLayer
dimensions : 960, 840, 806400 (nrow, ncol, ncell)
resolution : 0.008333333, 0.008333333 (x, y)
extent : -116, -109, 22, 30 (xmin, xmax, ymin, ymax)
crs : +proj=longlat +datum=WGS84 +no_defs
source : memory
names : alt_22
values : 206, 404 (min, max)
Then we can plot overlay plots of each (notice how I hid the legend for the first alt raster).
plot( alt, col="gray", legend=FALSE, xlab="Longitude", ylab="Latitude")
plot( alt_band, add=TRUE )

Option 2 - Manipulate the Data Frames
We can also proceed by relying upon the data.frame objects representing the elevation. So let’s go back to our the alt.df object and use that in combination with a filter and plot both data.frame objects (the outline of the landscape in gray and the elevation range as a gradient). I then overlay the beetle data with the ratios as sizes and label the locales with ggrepel. Notice here that you can use the sf::geometry object from beetles if you pass it through the st_coordinates function as a statistical tranform making it regular coordinates and not sf objects (yes this is kind of a trick and hack but KEEP IT HANDY!).
library( ggrepel )
alt.df %>%
filter( Elevation >= 205,
Elevation <= 405) %>%
ggplot() +
geom_raster( aes( x = Longitude,
y = Latitude),
fill = "gray80",
data=alt.df ) +
geom_raster( aes( x = Longitude,
y = Latitude,
fill = Elevation ) ) +
scale_fill_gradient2( low = "darkolivegreen",
mid = "yellow",
high = "brown",
midpoint = 305 ) +
geom_sf( aes(size=MFRatio),
alpha=0.5,
color="dodgerblue3",
data=beetles) +
geom_text_repel( aes( label = Site,
geometry = geometry),
data = beetles,
stat = "sf_coordinates",
size = 4,
color = "dodgerblue4") +
coord_sf() +
theme_minimal()
Warning in st_point_on_surface.sfc(sf::st_zm(x)): st_point_on_surface may not
give correct results for longitude/latitude data

Very nice indeed.
LS0tCnRpdGxlOiAiUmFzdGVycyIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgICBjc3M6ICJlbnZzNTQzLXN0eWxlcy5jc3MiCi0tLQo8Y2VudGVyPgohW1Jhc3RlcnMhXShodHRwczovL2xpdmUuc3RhdGljZmxpY2tyLmNvbS82NTUzNS81MDUxMDc1NzgzN19jMzYwNjY4MmFjX2NfZC5qcGcpCjwvY2VudGVyPgoKQWxsIHJhc3RlciBvcGVyYXRpb25zIGluIHRoaXMgdG9waWMgYXJlIGFjY29tcGxpc2hlZCB1c2luZyB0aGUgYHJhc3RlcmAgbGlicmFyeS4KCmBgYHtyfQpsaWJyYXJ5KHJhc3RlcikKYGBgCgpSYXN0ZXIgYXJlIHJlcHJlc2VudGF0aW9ucyBvZiBjb250aW51b3VzLCBvciBzZW1pLWNvbnRpbnVvdXMsIGRhdGEuICBUWW91IGNhbiBlbnZpc2lvbiBhIHJhc3RlciBqdXN0IGxpa2UgYW4gaW1hZ2UuICBXaGVuIG1lIG1ha2UgYSBgbGVhZmxldCgpYCBtYXAgYW5kIGhvdyB0aGUgdGlsZXMsIGVhY2ggcGl4ZWwgaXMgY29sb3JlZCBhIHBhcnRpY3VsYXIgdmFsdWUgcmVwcmVzZW50aW5nIGVsZXZhdGlvbiwgdGVtcGVyYXR1cmUsIHByZWNpcGl0YXRpb24sIGhhYml0YXQgdHlwZSwgb3Igd2hhdGV2ZXIuICBUaGlzIGlzIGV4YWN0bHkgdGhlIHNhbWUgZm9yIHJhc3RlcnMuICBUaGUgKmtleSBwb2ludCogaGVyZSBpcyB0aGF0IGVhY2ggcGl4ZWwgcmVwcmVzZW50cyBzb21lIGRlZmluZWQgcmVnaW9uIG9uIHRoZSBlYXJ0aCBhbmQgYXMgc3VjaCB0aGUgcmFzdGVyIGl0c2VsZiBpcyBnZW9yZWZlcmVuY2VkLiAgSXQgaGFzIGEgY29vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtIChDUlMpLCBib3VuZGFyaWVzLCBldGMuCgojIyBNYWtpbmcgUmFzdGVycyAqZGUgbm92byoKCkEgcmFzdGVyIGlzIHNpbXBseSBhIG1hdHJpeCB3aXRoIHJvd3MgYW5kIGNvbHVtbnMgYW5kIGVhY2ggZWxlbWVudCBoYXMgYSB2YWx1ZSBhc3NvY2lhdGVkIHdpdGggaXQuICBZb3UgY2FuIGNyZWF0ZSBhIHJhc3RlciAqZGUgbm92byogYnkgbWFraW5nIGEgbWF0cml4IG9mIGRhdGEgYW5kIGZpbGxpbmcgaXQgd2l0aCB2YWx1ZXMsIHRoZW4gdHVybmluZyBpdCBpbnRvIGEgcmFzdGVyLgoKSGVyZSBJIG1ha2UgYSByYXN0ZXIgd2l0aCByYW5kb20gbnVtYnJlcnMgc2VsZWN0ZWQgZnJvbSB0aGUgUG9pc3NvbiBEaXN0cmlidXRpb24gKGZpc2h5LCBJIGtub3cpIHVzaW5nIHRoZSBgcnBvaXMoKWAgZnVuY3Rpb24uICBJIHRoZW4gdHVybiBpdCBpbnRvIGEgbWF0cml4IHdpdGggNyByb3dzIChhbmQgNyBjb2x1bW5zKS4KCgpgYGB7cn0KdmFscyA8LSBycG9pcyg0OSwgbGFtYmRhPTEyKQp4IDwtIG1hdHJpeCggdmFscywgbnJvdz03KQp4CmBgYAoKV2hpbGUgd2UgaGF2ZW4ndCB1c2VkIG1hdHJpY2VzIG11Y2ggdGh1cyBmYXIsIGl0IGlzIGEgbG90IGxpa2UgYSBgZGF0YS5mcmFtZWAgd2l0aCByZXNwZWN0IHRvIGdldHRpbmcgYW5kIHNldHRpbmcgdmFsdWVzIHVzaW5nIG51bWVyaWNhbCBpbmRpY2VzLiAgRm9yIGV4YW1wbGUsIHRoZSB2YWx1ZSBvZiB0aGUgM3JkIHJvdyBhbmQgNXRoIGNvbHVtbiBpczoKCmBgYHtyfQp4WzMsNV0KYGBgCgpUbyBjb252ZXJ0IHRoaXMgc2V0IG9mIGRhdGEsIGFzIGEgbWF0cml4LCBpbnRvIGEgZ2Vvc3BhdGlhbGx5IHJlZmVyZW5jZWQgYHJhc3RlcigpYCBvYmplY3Qgd2UgZG8gdGhlIGZvbGxvd2luZzoKCmBgYHtyfQpyIDwtIHJhc3RlciggeCApCnIKYGBgCgpOb3RpY2UgdGhhdCB3aGVuIEkgcGxvdCBpdCBvdXQsIGl0IGRvZXMgbm90IHNob3cgdGhlIGRhdGEsIGJ1dCBhIHN1bW1hcnkgb2YgdGhlIGRhdGEgYWxvbmcgd2l0aCBzb21lIGtleSBkYXRhIGFib3V0IHRoZSBjb250ZW50cywgaW5jbHVkaW5nOiAgCi0gQSBjbGFzcyBkZWZpbml0aW9uICAKLSBUaGUgZGltZW5zaW9ucyBvZiB0aGUgdW5kZXJseWluZyBkYXRhIG1hdHJpeCwgIAotIFRoZSByZXNvbHV0aW9uIChlLmcuLCB0aGUgc3BhdGlhbCBleHRlbnQgb2YgdGhlIHNpZGVzIG9mIGVhY2ggcGl4ZWwpLiAgU2luY2Ugd2UgaGF2ZSBubyBDUlMgaGVyZSwgaXQgaXMgZXF1YWwgdG8gJG5yb3dzKHgpXnstMX0kIGFuZCAkbmNvbHMoeCleey0xfSQuICAKLSBUaGUgZXh0ZW50ICh0aGUgYm91bmRpbmcgYm94KSBhbmQgYWdhaW4gc2luY2Ugd2UgZG8gbm90IGhhdmUgYSBDUlMgZGVmaW5lZCBpdCBqdXN0IGdvZXMgZnJvbSAkMCQgdG8gJDEkLgotIFRoZSBgY3JzYCAobWlzc2luZykKLSBUaGUgc291cmNlIGNhbiBiZSBlaXRoZXIgYG1lbW9yeWAgaWYgdGhlIHJhc3RlciBpcyBub3QgdGhhdCBiaWcgb3IgYG91dCBvZiBtZW1vcnlgIGlmIGl0IGlzIGp1c3QgcmVmZXJlbmNpbmcuCgpJZiB0aGVzZSBkYXRhIHJlcHJlc2VudCBzb21ldGhpbmcgb24gdGhlIHBsYW5ldCwgd2UgY2FuIGFzc2lnbiB0aGUgZGltZW5zaW9ucyBhbmQgYENSU2AgdmFsdWVzIHRvIGl0IGFuZCB1c2UgaXQgaW4gb3VyIG5vcm1hbCBkYXktdG8tZGF5IG9wZXJhdGlvbnMuCgojIyBMb2FkaW5nIFJhc3RlcnMgZnJvbSBGaWxlcyBvciBVUkxzCgpXZSBjYW4gYWxzbyBncmFiIGEgcmFzdGVyIG9iamVjdCBmcm9tIHRoZSBmaWxlc3lzdGVtIG9yIGZyb20gc29tZSBvbmxpbmUgcmVwb3NpdG9yeSBieSBwYXNzaW5nIHRoZSBsaW5rIHRvIHRoZSBgcmFzdGVyKClgIGZ1bmN0aW9uLiAgSGVyZSBpcyB0aGUgZWxldmF0aW9uLCBpbiBtZXRlcnMsIG9mIHRoZSByZWdpb24gaW4gd2hpY2ggTWV4aWNvIGlzIGZvdW5kLiBUbyBsb2FkIGl0IGluLCBwYXNzIHRoZSB1cmwuCgpgYGB7cn0KdXJsIDwtICJodHRwczovL2dpdGh1Yi5jb20vZHllcmxhYi9FTlZTLUxlY3R1cmVzL3Jhdy9tYXN0ZXIvZGF0YS9hbHRfMjIudGlmIgpyIDwtIHJhc3RlciggdXJsICkKcgpgYGAKCk5vdGljZSB0aGF0IHRoaXMgcmFzdGVyIGhhcyBhIGRlZmluZWQgQ1JTIGFuZCBhcyBzdWNoIGl0IGlzIHByb2plY3RlZCBhbmQgdGhlIGV4dGVudCByZWxhdGVzIHRvIHRoZSB1bml0cyBvZiB0aGUgZGF0dW0gKGUuZy4sIGZyb20gLTEyMCB0byAtOTAgZGVncmVlcyBsb25naXR1ZGUgYW5kIDAgdG8gMzAgZGVncmVlcyBsYXRpdHVkZSkuCgpJZiB3ZSBwbG90IGl0LCB3ZSBjYW4gc2VlIHRoZSB3aG9sZSByYXN0ZXIuCgpgYGB7cn0KcGxvdChyKQpgYGAKCk5vdywgdGhpcyByYXN0ZXIgaXMgZWxldmF0aW9uIHdoZXJlIHRoZXJlIGlzIGxhbmQgYnV0IHdoZXJlIHRoZXJlIGlzIG5vIGxhbmQsIGl0IGlzIGZ1bGwgb2YgYE5BYCB2YWx1ZXMuICBBcyBzdWNoLCB0aGVyZSBpcyBhIHRvbiBvZiB0aGVtLgoKYGBge3J9CmZvcm1hdCggc3VtKCBpcy5uYSggdmFsdWVzKHIpICkgKSwgYmlnLm1hcmsgPSAiLCIgKQpgYGAKCgojIyBDcm9wcGluZwoKT25lIG9mIHRoZSBmaXJzdCB0aGluZ3MgdG8gZG8gaXMgdG8gY3JvcCB0aGUgZGF0YSBkb3duIHRvIHJlcHJlc2VudCB0aGUgc2l6ZSBhbmQgZXh0ZW50IG9mIG91ciBzdHVkeSBhcmVhLiAgSWYgd2Ugb3ZlciAxMCBtaWxsaW9uIG1pc3NpbmcgZGF0YSBwb2ludHMgKHRoZSBvY2VhbikgYW5kIG1vc3Qgb2YgTWV4aWNvIGluIHRoaXMgcmFzdGVyIGFib3ZlIGJ1dCB3ZSBhcmUgb25seSB3b3JraW5nIHdpdGggc2l0ZXMgaW4gQmFqYSBDYWxpZm9ybmlhIChOb3J0ZSB5IFN1ciksIHdlIHdvdWxkIGRvIHdlbGwgdG8gZXhjaXNlIChvciBjcm9wKSB0aGUgcmFzdGVyIHRvIG9ubHkgaW5jbHVkZSB0aGUgYXJlYSB3ZSBhcmUgaW50ZXJlc3RlZCBpbiB3b3JraW5nIHdpdGguICAKClRvcCBkbyB0aGlzLCB3ZSBuZWVkIHRvIGZpZ3VyZSBvdXQgYSBib3VuZGluZyBib3ggKGUuZy4sIHRoZSBtaW5pbWltIGFuZCBtYXhpbXVtIHZhbHVlcyBvZiBsb25naXR1ZGUgYW5kIGxhdGl0dWRlIHRoYXQgZW5jbG9zZSBvdXIgZGF0YSkuICBMZXQncyBhc3N1bWUgd2UgYXJlIHdvcmtpbmcgd2l0aCB0aGUgQmVldGxlIERhdGEgZnJvbSB0aGUgW1NwYXRpYWwgUG9pbnRzIFNsaWRlc10oaHR0cHM6Ly9keWVybGFiLmdpdGh1Yi5pby9FTlZTLUxlY3R1cmVzL3NwYXRpYWwvc3BhdGlhbF9wb2ludHMvc2xpZGVzLmh0bWwjMSkgYW5kIGxvYWQgaW4gdGhlIFNleC1iaWFzZWQgZGlzcGVyc2FsIGRhdGEgc2V0IGFuZCB1c2UgdGhvc2UgcG9pbnRzIGFzIGEgc3RhcnRpbmcgZXN0aW1hdGUgb2YgdGhlIGJvdW5kaW5nIGJveC4KCgpgYGB7cn0KbGlicmFyeSggc2YgKQpsaWJyYXJ5KCB0aWR5dmVyc2UgKQpiZWV0bGVfdXJsIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZHllcmxhYi9FTlZTLUxlY3R1cmVzL21hc3Rlci9kYXRhL0FyYXB0dXNfRGlzcGVyYWxfQmlhcy5jc3YiCgpyZWFkX2NzdiggYmVldGxlX3VybCApICU+JQogIHN0X2FzX3NmKCBjb29yZHM9YygiTG9uZ2l0dWRlIiwiTGF0aXR1ZGUiKSwgY3JzPTQzMjYgKSAtPiBiZWV0bGVzCgpzdW1tYXJ5KCBiZWV0bGVzICkKYGBgCgpOb3csIHdlIGNhbiB0YWtlIHRoZSBib3VuZGluZyBib3ggb2YgdGhlc2UgcG9pbnRzIGFuZCBnZXQgYSBmaXJzdCBhcHByb3hpbWF0aW9uLgoKYGBge3J9CmJlZXRsZXMgJT4lIHN0X2Jib3goKQpgYGAKCk9LLCBzbyB0aGlzIGlzIHRoZSBzdHJpY3QgYm91bmRpbmcgYm94IGZvciB0aGVzZSBwb2ludHMuICBUaGlzIG1lYW5zIHRoYXQgdGhlIG1pbmltdW0gYW5kIG1heGltdW0gdmFsdWVzIGZvciB0aGVzZSBwb2ludHMgYXJlIGRlZmluZWQgYnkgdGhlIG9yaWdpbmFsIGxvY2F0aW9uc+KAlGZvciBib3RoIHRoZSBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIChib3RoIG1pbmltdW0gYW5kIG1heGltdW0p4oCUd2UgaGF2ZSBzaXRlcyBvbiBlYWNoIG9mIHRoZSBlZGdlcy4gIFRoaXMgaXMgZmluZSBoZXJlIGJ1dCB3ZSBjb3VsZCBwcm9iYWJseSBhZGQgYSBsaXR0bGUgYml0IG9mIGEgKmJ1ZmZlciogYXJvdW5kIHRoYXQgYm91bmRpbmcgYm94IHNvIHRoYXQgd2UgZG8gbm90IGhhdmUgb3VyIHNpdGVzIG9uIHRoZSB2ZXJ5IGVkZ2Ugb2YgdGhlIHBsb3QuICBXZSBjYW4gZG8gdGhpcyBieSBlaXRoZXIgKmV5ZWJhbGxpbmctaXQqIHRvIHJvdW5kIHVwIHRvIHNvbWUgcmVhc29uYWJsZSBhcmVhIGFyb3VuZCB0aGUgcG9pbnRzICpvciogYXBwbHkgYSBidWZmZXIgKGBzdF9idWZmZXJgKSB0byB0aGUgdW5pb24gb2YgYWxsIHRoZSBwb2ludHMgd2l0aCBzb21lIGRpc3RhbmNlIGFuZCB0aGVuIHRha2UgdGhlIGJvb3VuZGluZyBib3guICAgSSdsbCBnbyBmb3IgdGhlIGZvcm1lciBhbmQgbWFrZSBpdCBpbnRvIGFuIGBleHRlbnRgIG9iamVjdC4KCmBgYHtyfQpiYWphX2V4dGVudCA8LSBleHRlbnQoIGMoLTExNiwgLTEwOSwgMjIsIDMwICkgKQpiYWphX2V4dGVudApgYGAKClRoZW4gd2UgY2FuIGBjcm9wKClgIHRoZSBvcmlnaW5hbCByYXN0ZXIgdXNpbmcgdGhpcyBleHRlbnQgb2JqZWN0IHRvIGNyZWF0ZSBvdXIgd29ya2luZyByYXN0ZXIuICBJIGNhbiB0aGVuIGR1bXAgbXkgcG9pbnRzIG9udG8gdGhlIHNhbWUgcmFzdGVyIHBsb3QgYnkgaW5kaWNhYXRpbmcgYGFkZD1UUlVFYAoKYGBge3J9CmFsdCA8LSBjcm9wKCByLCBiYWphX2V4dGVudCApCnBsb3QoYWx0KQpwbG90KCBiZWV0bGVzWyJTdWl0YWJpbGl0eSJdLCBwY2g9MTYsIGFkZD1UUlVFKQpgYGAKCjxkaXYgY2xhc3M9ImJveC1yZWQiPgo8dGFibGU+Cjx0cj4KPHRkPjxoMT7imqDvuI88L2gxPjwvdGQ+Cjx0ZD4gJm5ic3A7ICAmbmJzcDsgPC90ZD4KPHRkPllvdSBuZWVkIHRvIGJlIGNhcmVmdWwgaGVyZS4gIFdoZW4geW91IHVzZSBidWlsdC1pbiBncmFwaGljcyBwcm9jZXNzZXMgaW4gYSBtYXJrZG93biBkb2N1bWVudCBzdWNoIGFzIHRoaXMgYW5kIGludGVuZCB0byBhZGQgc3Vic2VxdWVudCBwbG90cyB0byBhbiBleGlzdGluZyBwbG90IHlvdSAqKmNhbm5vdCoqIHJ1biB0aGUgbGluZXMgaW5kaXZpZHVhbGwuICBUaGV5ICoqbXVzdCoqIGJlIGFsbCBleGVjdXRlZCBhcyB0aGUgd2hvbGUgY2h1bmsuICBTbyB0aGVyZSBpcyBubyA8dHQ+Q1RSTC9DTUQgKyBSRVRVUk48L3R0PiBhY3Rpb24gaGVyZSwgaXQgd2lsbCBwbG90IHRoZSBmaXJzdCBvbmUgYW5kIHRoZW4gY29tcGxhaW4gdGhyb3VnaG91dCB0aGUgcmVtYWluaW5nIG9uZXMgc2F5aW5nIHNvbWV0aGluZyBsaWtlIGBwbG90Lm5ldyBoYXMgbm90IGJlZW4gY2FsbGVkIHlldGAuICBTbyB5b3UgaGF2ZSB0byBlaXRoZXIga25pdCB0aGUgd2hvbGUgZG9jdW1lbnQgb3IganVzdCBydW4gdGhlIHdob2xlIGNodW5rIHRvIGdldCB0aGVtIHRvIG92ZXJsYXkuPC90ZD4KPC90cj4KPC90YWJsZT4KPC9kaXY+CgoKIyMgUGxvdHRpbmcgd2l0aCBHR1Bsb3QKCkFzIHlvdSBtYXkgc3VzcGVjdCwgb3VyIG9sZCBmcmllbmQgYGdncGxvdGAgaGFzIHNvbWUgdHJpY2tzIHVwIGl0cyBzbGVhdmUgZm9yIHVzLiAgVGhlIG1haW4gdGhpbmcgaGVyZSBpcyB0aGF0IGBnZ3Bsb3RgIHJlcXVpcmVzIGEgYGRhdGEuZnJhbWVgIG9iamVjdCBhbmQgYSByYXN0ZXIgaXMgbm90IGEgYGRhdGEuZnJhbWVgIC0tLSBVbmxlc3Mgd2UgdHVybiBpdCBpbnRvIG9uZSAoaGVoZWhlKSB1c2luZyBhIGNvb2wgZnVuY3Rpb24gY2FsbGVkIGByYXN0ZXJUb1BvaW50cygpYC4gIFRoaXMgdGFrZXMgdGhlIGNlbGxzIG9mIHRoZSByYXN0ZXIgKGFuZCB1bmRlcmx5aW5nIG1hdHJpeCkgYW5kIG1ha2VzIHBvaW50cyBmcm9tIGl0LgoKYGBge3J9CmFsdCAlPiUKICByYXN0ZXJUb1BvaW50cygpICU+JQogIGhlYWQoKQpgYGAKCkhvd2V2ZXIsIHRoZXkgYXJlIG5vdCBhIGBkYXRhLmZyYW1lYCBidXQgYSBtYXRyaXguCgpgYGB7cn0KYWx0ICU+JQogIHJhc3RlclRvUG9pbnRzKCkgJT4lCiAgY2xhc3MoKQpgYGAKClNvLCBpZiB3ZSBhcmUgZ29pbmcgdG8gdXNlIHRoaXMsIHcgbmVlZCB0byB0cmFuc2Zvcm0gaXQgZnJvbSBhIGBtYXRyaXhgIG9iamVjdCBpbnRvIGEgYGRhdGEuZnJhbWVgIG9iamVjdC4gIFdlIGNhbiBkbyB0aGlzIHVzaW5nIHRoZSBgYXMuZGF0YS5mcmFtZSgpYCBmdW5jdGlvbi4gIFJlbWVtYmVyIGZyb20gdGhlIFtsZWN0dXJlIG9uIGBkYXRhLmZyYW1lYCBvYmplY3RzXShodHRwczovL2R5ZXJsYWIuZ2l0aHViLmlvL0VOVlMtTGVjdHVyZXMvcl9sYW5ndWFnZS9kYXRhX2ZyYW1lcy9zbGlkZXMuaHRtbCMxKSB0aGF0IHdlIGNhbiBjb2VyY2UgY29sdW1ucyBvZiBkYXRhIChlaXRoZXIgYG1hdHJpeGAgb3IgYGFycmF5YCkgaW50byBhIGBkYXRhLmZyYW1lYCB0aGlzIHdheS4KCgpTbyBoZXJlIGl0IGlzIGluIG9uZSBwaXBlLCB1c2luZyB0aGUgZm9sbG93aW5nIHRyaWNrczogIAotIENvbnZlcnRpbmcgcmFzdGVyIHRvIHBvaW50cyBhbmQgdGhlbiB0byBgZGF0YS5mcmFtZWAgc28gaXQgd2lsbCBnbyBpbnRvIGBnZ3Bsb3RgICAgIAotIFJlbmFtaW5nIHRoZSBjb2x1bW5zIG9mIGRhdGEgSSBhbSBnb2luZyB0byBrZWVwIHNvIEkgZG9uJ3QgaGF2ZSB0byBtYWtlIGB4bGFiYCBhbmQgYHlsYWJgICAKCmBgYHtyfQphbHQgJT4lCiAgcmFzdGVyVG9Qb2ludHMoKSAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogIHRyYW5zbXV0ZShMb25naXR1ZGU9eCwKICAgICAgICAgICAgTGF0aXR1ZGU9eSwKICAgICAgICAgICAgRWxldmF0aW9uPWFsdF8yMikgIC0+IGFsdC5kZgpoZWFkKCBhbHQuZGYgKQpgYGAKClRoZW4gd2UgY2FuIHBsb3QgaXQgYnk6ICAKLSBQbG90dGluZyBpdCB1c2luZyBgZ2VvbV9yYXN0ZXIoKWAgYW5kIHNldHRpbmcgdGhlIGZpbGwgY29sb3IgdG8gdGhlIHZhbHVlIG9mIGVsZXZhdGlvbi4KLSBNYWtpbmcgdGhlIGNvb3JkaW5hdGVzIGVxdWFsIChlLmcuLCByb3VnaHRseSBlcXVhbCBpbiBhcmVhIGZvciBsb25naXR1ZGUgYW5kIGxhdGl0dWRlKSwgYW5kCi0gQXBwbHlpbmcgb25seSBhIG1pbmltYWwgdGhlbWUuCgoKCmBgYHtyfQphbHQuZGYgJT4lCiAgZ2dwbG90KCkgICsgCiAgZ2VvbV9yYXN0ZXIoIGFlcyggeCA9IExvbmdpdHVkZSwgCiAgICAgICAgICAgICAgICAgICAgeSA9IExhdGl0dWRlLCAKICAgICAgICAgICAgICAgICAgICBmaWxsID0gRWxldmF0aW9uKSApICsgCiAgY29vcmRfZXF1YWwoKSArCiAgdGhlbWVfbWluaW1hbCgpIC0+IGJhamFfZWxldmF0aW9uCgpiYWphX2VsZXZhdGlvbgpgYGAKCgoKClRoYXQgbG9va3MgZ29vZCBidXQgd2Ugc2hvdWxkIHByb2JhYmx5IGRvIHNvbWV0aGluZyB3aXRoIHRoZSBjb2xvcnMuICBUaGVyZSBpcyBhIGJ1aWx0LWluIGB0ZXJyYWluLmNvbG9ycygpYCBhbmQgdGVsbCBgZ2dwbG90YCB0byB1c2UgdGhpcyBmb3IgdGhlIGZpbGwgZ3JhZGllbnQuCgpgYGB7cn0KYmFqYV9lbGV2YXRpb24gKyAKICBzY2FsZV9maWxsX2dyYWRpZW50biggY29sb3JzPXRlcnJhaW4uY29sb3JzKDEwMCkpCmBgYAoKCk9yIHlvdSBjYW4gZ28gZGl2ZSBpbnRvIGNvbG9ycyBhbmQgc2V0IHlvdXIgb3duLCB5b3UgY2FuIHNldCB1cCB5b3VyIG93biBncmFkaWVudCBmb3IgYGdncGxvdGAgdXNpbmcgaW5kZXBlbmRlbnQgY29sb3JzIGFuZCB0aGVuIHRlbGwgaXQgd2hlcmUgdGhlIG1pZHBvaW50IGlzIGFsb25nIHRoYXQgZ3JhZGllbnQgYW5kIGl0IHdpbGwgKmRvIHRoZSByaWdodCB0aGluZzxzdXA+JmNvcHk7PC9jb3B5PiouCgpgYGB7cn0KYmFqYV9lbGV2YXRpb24gKyAKICBzY2FsZV9maWxsX2dyYWRpZW50MiggbG93ID0gImRhcmtvbGl2ZWdyZWVuIiwKICAgICAgICAgICAgICAgICAgICAgICAgbWlkID0gInllbGxvdyIsCiAgICAgICAgICAgICAgICAgICAgICAgIGhpZ2ggPSAiYnJvd24iLCAKICAgICAgICAgICAgICAgICAgICAgICAgbWlkcG9pbnQgPSAxMDAwICkgLT4gYmFqYV9tYXAKYmFqYV9tYXAKYGBgCgpOb3cgdGhhdCBsb29rcyBncmVhdC4gIE5vdywgaG93IGFib3V0IG92ZXJsYXlpbmcgdGhlIHBvaW50cyBvbnRvIHRoZSBwbG90IGFuZCBpbmRpY2F0ZSB0aGUgc2l6ZSBvZiB0aGUgcG9pbnQgYnkgdGhlICoq4pmC4pmAKiogcmF0aW8uCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQpiYWphX21hcCArIAogIGdlb21fc2YoIGFlcyhzaXplID0gTUZSYXRpbyApLCAKICAgICAgICAgICBkYXRhID0gYmVldGxlcywgCiAgICAgICAgICAgY29sb3IgPSAiZG9kZ2VyYmx1ZTIiLAogICAgICAgICAgIGFscGhhID0gMC43NSkgCmBgYApOb3cgdGhhdCBsb29rcyBuaWNlLgoKIyMgSWRlbnRpZnlpbmcgUG9pbnRzCgpZb3UgY2FuIGdldCBzb21lIGluZm9ybWF0aW9uIGZyb20gYSByYXN0ZXIgcGxvdCBpbnRlcmFjdGl2ZWx5IGJ5IHVzaW5nIHRoZSBgY2xpY2tgIGZ1bmN0aW9uLiAgVGhpcyAqKm11c3QqKiBiZSBkb25lIHdpdGggYW4gYWN0aXZlIHJhc3RlciBwbG90LiAgQWZ0ZXIgdGhhdCwgeW91IHVzZSB0aGUgYGNsaWNrKClgIGZ1bmN0aW9uIHRvIGdyYWIgd2hhdCB5b3UgbmVlZC4gIFlvdXIgbW91c2Ugd2lsbCB0dXJuIGZyb20gYW4gYXJyb3cgaW50byBhIGNyb3NzIGhhaXIgYW5kIHlvdSBjYW4gcG9zaXRpb24gaXQgd2hlcmUgeW91IGxpa2UgYW5kIGdldCBpbmZvcm1hdGlvbiBzdWNoIGFzIHRoZSBjb3JyZGluYXRlcyAoc3BhdGlhbCkgb2YgdGhlIHBvaW50IGFuZCB0aGUgdmFsdWUgb2YgdGhlIHJhc3RlciBwaXhlbCBhdCB0aGF0IGxvY2F0aW9uLgoKSWYgeW91IGRvIG5vdCBzcGVjaWZ5IGBuPWAgaW4gdGhlIGZ1bmN0aW9uIHRoZW4gaXQgd2lsbCBjb250aW51ZSB0byBjb2xsZWN0IGRhdGEgdW50aWwgeW91IGNsaWNrIG91dHNpZGUgdGhlIGdyYXBoaW5nIGFyZWEuICBJZiB5b3Ugc2V0IGBpZD1UUlVFYCBpdCB3aWxsIHBsb3QgdGhlIG51bWJlciBvZiB0aGUgcG9pbnQgb250byB0aGUgbWFwIHNvIHlvdSBjYW4gc2VlIHdoZXJlIHlvdSBoYWQgY2xpY2tlZC4gIFNpbmNlIHRoaXMgaXMgaW50ZXJhY3RpdmUsIHlvdSB3aWxsIG5vdCBzZWUgdGhlIHByb2Nlc3Mgd2hlbiB5b3UgZXhlY3V0ZSB0aGUgY29kZSBiZWxvdywgYnV0IGl0IHdpbGwgbG9vayBsaWtlLgoKYGBge3IsIGV2YWw9RkFMU0V9CnBsb3QoIGFsdCApCmNsaWNrKGFsdCwgeHk9VFJVRSwgdmFsdWU9VFJVRSwgbj0zICkgLT4gcG9pbnRzCmBgYAoKIVttYXAgd2l0aCBwb2ludHNdKGh0dHBzOi8vbGl2ZS5zdGF0aWNmbGlja3IuY29tLzY1NTM1LzUwNTA1NTA1OTQ4XzA4ZTNlOTFkZmJfY19kLmpwZykKYGBge3IgZWNobz1GQUxTRX0KcG9pbnRzIDwtIGRhdGEuZnJhbWUoIHggPSBjKC0xMTMuNjI5MiwgLTExMi40NzkyLCAtMTExLjI0NTgsIC0xMDkuOTk1OCksCiAgICAgICAgICAgICAgICAgICAgICB5ID0gYygyOC40NTQxNywgMjYuODU0MTcsIDI0LjgzNzUwLCAyMy40ODc1MCksCiAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGMoODcwLCAxMTg1LCAxMzUsIDExNDUpICkKYGBgCgpIZXJlIGFyZSB3aGF0IHRoZSBwb2ludHMgbG9vayBsaWtlLiAgCgpgYGB7cn0KcG9pbnRzCmBgYAoKSSdtIGdvaW5nIHRvIHJlbmFtZSB0aGUgY29sdW1uIG5hbWVzCgpgYGB7cn0KcG9pbnRzICU+JQogIHRyYW5zbXV0ZSggTG9uZ2l0dWRlID0geCwKICAgICAgICAgICAgIExhdGl0dWRlID0geSwKICAgICAgICAgICAgIFZhbHVlID0gdmFsdWUpIC0+IHNpdGVzCmBgYAoKQW5kIHRoZW4gSSBjYW4gcGxvdCB0aG9zZSBwb2ludHMgKHVzaW5nIGBnZW9tX3BvaW50KClgKSBvbnRvIG91ciBiYWNrZ3JvdW5kIG1hcC4KCmBgYHtyfQpiYWphX21hcCArIAogIGdlb21fcG9pbnQoIGFlcyh4ID0gTG9uZ2l0dWRlLAogICAgICAgICAgICAgICAgICB5ID0gTGF0aXR1ZGUsIAogICAgICAgICAgICAgICAgICBzaXplID0gVmFsdWUpLCBkYXRhPXNpdGVzLCBjb2xvcj0icmVkIikgCmBgYAoKTWV4ZWxsZW50ISAKCiMjIFJlcHJvamVjdGluZyBSYXN0ZXJzIAoKSnVzdCBsaWtlIHBvaW50cywgd2UgY2FuIHJlcHJvamVjdCB0aGUgZW50aXJlIHJhc3RlciB1c2luZyB0aGUgYHByb2plY3RSYXN0ZXJgIGZ1bmN0aW9uLiAgSEplcmUgSSBhbSBnb2luZyB0byBwcm9qZWN0IHRoZSByYXN0ZXIgaW50byBVVE0gWm9uZSAxMk4sIGEgY29tbW9uIHByb2plY3Rpb24gZm9yIHRoaXMgcGFydCBvZiBbTWV4aWNvIGZyb20gZXBzZy5pb10oaHR0cHM6Ly9lcHNnLmlvLzYzNjcpLiAgCgpVbmZvcnR1bmF0bHksIHRoZSBgcmFzdGVyYCBsaWJyYXJ5IGRvZXMgbm90IHVzZSBlcHNnIGNvZGVzIHNvIHdlJ2xsIGhhdmUgdG8gdXNlIHRoZSBsYXJnZSBkZXNjcmlwdGlvbiBvZiB0aGF0IHByb2plY3Rpb24uICBTZWUgdGhlIFtwYWdlXShodHRwczovL2Vwc2cuaW8vNjM2NykgZm9yIHRoaXMgcHJvamVjdGlvbiBhbmQgc2Nyb2xsIGRvd24gdG8gdGhlIHByb2ouNCBkZWZpbml0aW9uLiAgCgpgYGB7cn0KbmV3LnByb2ogPC0gIitwcm9qPXV0bSArem9uZT0xMiArZWxscHM9R1JTODAgK3Rvd2dzODQ9MCwwLDAsMCwwLDAsMCArdW5pdHM9bSArbm9fZGVmcyAiCmBgYAoKQ29weSB0aGlzIGludG8gYSBjaGFyYWN0ZXIgdmFyaWFibGUgYW5kIHRoZW4gdXNlIHRoZSBgcHJvamVjdFJhc3RlcigpYCBmdW5jdGlvbiBhbmQgYXNzaWduIHRoYXQgbmV3IHZhbHVlIGFzIHRoZSBgQ1JTYC4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmFsdC51dG0gPC0gcHJvamVjdFJhc3RlciggYWx0LCBjcnM9bmV3LnByb2opCnBsb3QoIGFsdC51dG0sIHhsYWI9IkVhc3RpbmciLCB5bGFiPSJOb3J0aGluZyIgKQpgYGAKCkVhc3kuCgoKIyMgUmFzdGVyIE9wZXJhdGlvbnMKCk9LLCBzbyBub3cgd2UgY2FuIG1ha2UgYW5kIHNob3cgYSByYXN0ZXIgYnV0IHdoYXQgYWJvdXQgZG9pbmcgc29tZSBvcGVyYXRpb25zPyAgQSByYXN0ZXIgaXMganVzdCBhIG1hdHJpeCAqZGVjb3JhdGVkKiB3aXRoIG1vcmUgZ2Vvc3BhdGlhbCBpbmZvcm1hdGlvbi4gIFRoaXMgYWxsb3dzIHVzIHRvIGRvIG5vcm1hbCBgUmAgbGlrZSBkYXRhIG1hbmlwdWxhdGlvbnMgb24gdGhlIHVuZGVybHlpbmcgZGF0YS4gIAoKQ29uc2lkZXIgdGhlIGZvbGxvd2luZyBxdWVzdGlvbi4gIAoKPiBXaGF0IGFyZSB0aGUgcGFydHMgb2YgQmFqYSBDYWxpZm9ybmlhIHRoYXQgYXJlIHdpdGhpbiAxMDBtIG9mIHRoZSBlbGV2YXRpb24gb2Ygc2l0ZSBuYW1lZCAqU2FuIEZyYW5jaXNxdWl0byogKGBzZnJhbmApPyAgCgpUbyBhbnN3ZXIgdGhpcywgd2UgaGF2ZSB0aGUgZm9sbG93aW5nIGdlbmVyYWwgb3V0bGluZSBvZiBvcGVyYXRpb25zLgoKMS4gRmluZCB0aGUgY29vcmRpbmF0ZXMgb2YgdGhlIHNpdGUgbmFtZWQgYHNmcmFuYCAgCjIuIEV4dHJhY3QgdGhlIGVsZXZhdGlvbiBmcm9tIHRoZSBgYWx0YCByYXN0ZXIgdGhhdCBpcyB3aXRoaW4gMTAwbSAoKy8tKSBvZiB0aGF0IHNpdGUuCjMuIFBsb3QgdGhlIHdob2xlIGJhamEgZGF0YSBhcyBhIGJhY2tncm91bmQgIAo0LiBPdmVybGF5IGFsbCB0aGUgbG9jYXRpb25zIHdpdGhpbiB0aGF0IGVsZXZhdGlvbiBiYW5kLgoKVG8gZG8gdGhpcyB3ZSB3aWxsIHVzZSBib3RoIHRoZSBgYWx0YCBhbmQgdGhlIGBiZWV0bGVzYCBkYXRhIG9iamVjdHMuCgpGaXJzdCwgd2UgZmluZCBvdXQgdGhlIGNvb3JkaW5hdGVzIG9mIHRoZSBzaXRlLgoKYGBge3J9CnNmcmFuIDwtIGJlZXRsZXMkZ2VvbWV0cnlbIGJlZXRsZXMkU2l0ZSA9PSAic2ZyYW4iXQpzZnJhbgpgYGAKCgpOb3csIHdlIG5lZWQgdG8gZmlndXJlIG91dCB3aGF0IHRoZSB2YWx1ZSBvZiBlbGV2YXRpb24gaW4gdGhlIGBhbHRgIHJhc3RlciBpcyBhdCB0aGlzIHNpdGUuICBUaGlzIGNhbiBiZSBkb25lIHdpdGggdGhlIGBleHRyYWN0KClgIGZ1bmN0aW9uIGZyb20gdGhlIGByYXN0ZXJgIGxpYnJhcnkuICAKCioqSG93ZXZlcioqLCB0aGUgdGhpcyBmdW5jdGlvbiBkb2Vzbid0IHdvcmsgZGlyZWN0bHkgd2l0aCBgc2ZgIG9iamVjdHMgc28gd2UgbmVlZCB0byBjYXN0IGl0IGludG8gYSBgU3BhdGlhbGAgb2JqZWN0W14xXS4gRm9ydHVuYXRseSwgdGhhdCBpcyBhIHByZXR0eSBlYXN5IGNvZXJjaW9uLgoKYGBge3J9CnJhc3Rlcjo6ZXh0cmFjdChhbHQsIGFzKHNmcmFuLCJTcGF0aWFsIikgKSAKYGBgCgoKPGRpdiBjbGFzcz0iYm94LXllbGxvdyI+KipXYXJuaW5nOioqIGluIHRoZSBhYm92ZSBjb2RlLCBJIHVzZWQgdGhlIGZ1bmN0aW9uIGBleHRyYWN0KClgIHRvIGV4dHJhY3QgdGhlIGRhdGEgZnJvbSB0aGUgYGFsdGAgcmFzdGVyIGZvciB0aGUgY29vcmRpbmF0ZSBvZiB0aGUgdGFyZ2V0IGxvY2FsZS4gIEhvd2V2ZXIsIHRoZXJlIGlzIGFsc28gYW4gYGV4dHJhY3QoKWAgZnVuY3Rpb24gdGhhdCBoYXMgYmVlbiBicm91Z2h0IGluIGZyb20gdGhlIGBkcGx5cmAgbGlicmFyeSAoYXMgcGFydCBvZiBgdGlkeXZlcnNlYCkuICBJbiB0aGlzIGZpbGUsIEkgbG9hZGVkIGBsaWJyYXJ5KHJhc3RlcilgIGJlZm9yZSBgbGlicmFyeSh0aWR5dmVyc2UpYCBhbmQgYXMgc3VjaCB0aGUgYGRwbHlyOjpleHRyYWN0KClgIGZ1bmN0aW9uIGhhcyBvdmVycmlkZGVuIHRoZSBvbmUgZnJvbSBgcmFzdGVyYOKAlHRoZXkgY2Fubm90ICpib3RoKiBiZSBhdmFpbGFibGUuICBBcyBhIGNvbnNlcXVlbmNlLCBJIHVzZSB0aGUgZnVsbCBuYW1lIG9mIHRoZSBmdW5jdGlvbiB3aXRoIGBwYWNrYWdlOjpmdW5jdGlvbmAgd2hlbiBJIGNhbGwgaXQgYXMgYHJhc3Rlcjo6ZXh0cmFjdCgpYCB0byByZW1vdmUgYWxsIGFtYmlndWl0eS4gIElmIEkgaGFkIG5vdCwgSSBnb3QgYSBtZXNzYWdlIHNheWluZyBzb21ldGhpbmcgbGlrZSwgYEVycm9yIGluIFVzZU1ldGhvZCgiZXh0cmFjdF8iKSA6IG5vIGFwcGxpY2FibGUgbWV0aG9kIGZvciAnZXh0cmFjdF8nIGFwcGxpZWQgdG8gYW4gb2JqZWN0IG9mIGNsYXNzICJjKCdSYXN0ZXJMYXllcicsICdSYXN0ZXInLCAnQmFzaWNSYXN0ZXInKSJgLiAgTm93LCBJIGtub3cgdGhlcmUgaXMgYW4gYGV4dHJhY3QoKWAgZnVuY3Rpb24gaW4gYHJhc3RlcmAgc28gdGhpcyBpcyB0aGUgKipkZWFkIGdpdmVhd2F5KiogdGhhdCBpdCBoYXMgYmVlbiBvdmVyd3JpdHRlbiBieSBhIHN1YnNlcXVlbnQgbGlicmFyeSBjYWxsLiAgCjwvZGl2PgoKIyMjIE9wdGlvbiAxIC0gTWFuaXB1bGF0ZSB0aGUgUmFzdGVyCgpUbyB3b3JrIG9uIGEgcmFzdGVyIGRpcmVjdGx5LCB3ZSBjYW4gYWNjZXNzIHRoZSB2YWx1ZXMgd2l0aGluIGl0IHVzaW5nIHRoZSBgdmFsdWVzKClgIGZ1bmN0aW9uIChJIGtub3csIHRoZXNlIHN0YXRpc3RpY2FuL3Byb2dyYW1tZXJzIGFyZSBxdWl0ZSBjbGVhdmVyKS4KClNvLCB0byBtYWtlIGEgY29weSBhbmQgbWFrZSBvbmx5IHRoZSB2YWx1ZXMgdGhhdCBhcmUgKy8tIDEwMG0gb2YgYHNmcmFuYCB3ZSBjYW4uCgpgYGB7cn0KYWx0X2JhbmQgPC0gYWx0CnZhbHVlcyggYWx0X2JhbmQgKVsgdmFsdWVzKGFsdF9iYW5kKSA8PSAyMDUgXSA8LSBOQQp2YWx1ZXMoIGFsdF9iYW5kIClbIHZhbHVlcyhhbHRfYmFuZCkgPj0gNDA1IF0gPC0gTkEKYWx0X2JhbmQKYGBgCgpUaGVuIHdlIGNhbiBwbG90IG92ZXJsYXkgcGxvdHMgb2YgZWFjaCAobm90aWNlIGhvdyBJIGhpZCB0aGUgbGVnZW5kIGZvciB0aGUgZmlyc3QgYGFsdGAgcmFzdGVyKS4KCmBgYHtyfQpwbG90KCBhbHQsIGNvbD0iZ3JheSIsIGxlZ2VuZD1GQUxTRSwgeGxhYj0iTG9uZ2l0dWRlIiwgeWxhYj0iTGF0aXR1ZGUiKQpwbG90KCBhbHRfYmFuZCwgYWRkPVRSVUUgKQpgYGAKCgojIyMgT3B0aW9uIDIgLSBNYW5pcHVsYXRlIHRoZSBEYXRhIEZyYW1lcwoKV2UgY2FuIGFsc28gcHJvY2VlZCBieSByZWx5aW5nIHVwb24gdGhlIGBkYXRhLmZyYW1lYCBvYmplY3RzIHJlcHJlc2VudGluZyB0aGUgZWxldmF0aW9uLiAgU28gbGV0J3MgZ28gYmFjayB0byBvdXIgdGhlIGBhbHQuZGZgIG9iamVjdCBhbmQgdXNlIHRoYXQgaW4gY29tYmluYXRpb24gd2l0aCBhIGBmaWx0ZXJgIGFuZCBwbG90IGJvdGggYGRhdGEuZnJhbWVgIG9iamVjdHMgKHRoZSBvdXRsaW5lIG9mIHRoZSBsYW5kc2NhcGUgaW4gZ3JheSBhbmQgdGhlIGVsZXZhdGlvbiByYW5nZSBhcyBhIGdyYWRpZW50KS4gIEkgdGhlbiBvdmVybGF5IHRoZSBiZWV0bGUgZGF0YSB3aXRoIHRoZSByYXRpb3MgYXMgc2l6ZXMgYW5kIGxhYmVsIHRoZSBsb2NhbGVzIHdpdGggYGdncmVwZWxgLiAgTm90aWNlIGhlcmUgdGhhdCB5b3UgY2FuIHVzZSB0aGUgYHNmOjpnZW9tZXRyeWAgb2JqZWN0IGZyb20gYGJlZXRsZXNgIGlmIHlvdSBwYXNzIGl0IHRocm91Z2ggdGhlIGBzdF9jb29yZGluYXRlc2AgZnVuY3Rpb24gYXMgYSBzdGF0aXN0aWNhbCB0cmFuZm9ybSBtYWtpbmcgaXQgcmVndWxhciBjb29yZGluYXRlcyBhbmQgbm90IGBzZmAgb2JqZWN0cyAoeWVzIHRoaXMgaXMga2luZCBvZiBhIHRyaWNrIGFuZCBoYWNrIGJ1dCBLRUVQIElUIEhBTkRZISkuCgpgYGB7ciBlY2hvPVRSVUV9CmxpYnJhcnkoIGdncmVwZWwgKQphbHQuZGYgJT4lCiAgZmlsdGVyKCBFbGV2YXRpb24gPj0gMjA1LAogICAgICAgICAgRWxldmF0aW9uIDw9IDQwNSkgJT4lCiAgZ2dwbG90KCkgKyAKICBnZW9tX3Jhc3RlciggYWVzKCB4ID0gTG9uZ2l0dWRlLAogICAgICAgICAgICAgICAgICAgIHkgPSBMYXRpdHVkZSksCiAgICAgICAgICAgICAgIGZpbGwgPSAiZ3JheTgwIiwgCiAgICAgICAgICAgICAgIGRhdGE9YWx0LmRmICkgKyAKICBnZW9tX3Jhc3RlciggYWVzKCB4ID0gTG9uZ2l0dWRlLAogICAgICAgICAgICAgICAgICAgIHkgPSBMYXRpdHVkZSwgCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IEVsZXZhdGlvbiApICkgKyAKICBzY2FsZV9maWxsX2dyYWRpZW50MiggbG93ID0gImRhcmtvbGl2ZWdyZWVuIiwKICAgICAgICAgICAgICAgICAgICAgICAgbWlkID0gInllbGxvdyIsCiAgICAgICAgICAgICAgICAgICAgICAgIGhpZ2ggPSAiYnJvd24iLCAKICAgICAgICAgICAgICAgICAgICAgICAgbWlkcG9pbnQgPSAzMDUgKSArCiAgZ2VvbV9zZiggYWVzKHNpemU9TUZSYXRpbyksIAogICAgICAgICAgIGFscGhhPTAuNSwgCiAgICAgICAgICAgY29sb3I9ImRvZGdlcmJsdWUzIiwgCiAgICAgICAgICAgZGF0YT1iZWV0bGVzKSArCiAgZ2VvbV90ZXh0X3JlcGVsKCBhZXMoIGxhYmVsID0gU2l0ZSwKICAgICAgICAgICAgICAgICAgICAgICAgZ2VvbWV0cnkgPSBnZW9tZXRyeSksCiAgICAgICAgICAgICAgICAgICBkYXRhID0gYmVldGxlcywKICAgICAgICAgICAgICAgICAgIHN0YXQgPSAic2ZfY29vcmRpbmF0ZXMiLCAKICAgICAgICAgICAgICAgICAgIHNpemUgPSA0LCAKICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImRvZGdlcmJsdWU0IikgKyAKICBjb29yZF9zZigpICsgCiAgdGhlbWVfbWluaW1hbCgpIApgYGAKCgoKVmVyeSBuaWNlIGluZGVlZC4KCgoKW14xXTogQSBgU3BhdGlhbGAgb2JqZWN0IGlzIGZyb20gdGhlIGBzcGAgbGlicmFyeS4gIFRoaXMgaXMgYW4gb2xkZXIgbGlicmFyeSB0aGF0IGlzIHN0aWxsIHVzZWQgYnkgc29tZS4gIEl0IGlzIGEgcm9idXN0IGxpYnJhcnkgKmJ1dCogaXQgaXMgcHV0IHRvZ2V0aGVyIGluIGEgc2xpZ2h0bHkgZGlmZmVyZW50IHdheSB0aGF0IGNvbXBsaWNhdGVzIHNpdHVhdGlvbnMgYSBiaXQsIHdoaWNoIGlzIG5vdCB3aHkgd2UgYXJlIGNvdmVyaW5nIGl0IGluIHRoaXMgdG9waWMu